<?php
// 将本段保存为 api.php
header('Content-Type: application/json; charset=utf-8');
mb_internal_encoding('UTF-8');

define('BASE_DIR', __DIR__ . DIRECTORY_SEPARATOR . 'storage');
define('TMP_DIR', __DIR__ . DIRECTORY_SEPARATOR . 'tmp');

autoInit();
$action = $_REQUEST['action'] ?? '';

try {
  switch ($action) {
    case 'list': json(listing()); break;
    case 'mkdir': json(mkdir_p()); break;
    case 'rename': json(rename_item()); break;
    case 'delete': json(delete_items()); break;
    case 'rmdir': json(delete_folder()); break;
    case 'thumb': thumb(); break; // 返回图像缩略图
    case 'raw': raw(); break;     // 原图/原文件（用于预览）
    case 'info': json(info()); break;
    case 'check': json(check_chunks()); break;
    case 'upload': json(upload_chunk()); break;
    case 'merge': json(merge_chunks()); break;
    case 'newfile': json(new_file()); break;
    case 'savefile': json(save_file()); break;
    case 'move': json(move_file()); break;
    case 'zip': json(zip_file()); break;
    case 'tree': json(dir_tree()); break;

    default: echo json_encode(['ok'=>false,'msg'=>'bad action']);
  }
} catch (Throwable $e) {
  http_response_code(500);
  echo json_encode(['ok'=>false,'msg'=>$e->getMessage()]);
}


function dir_tree(){
  $base = BASE_DIR;
  return ['ok'=>true, 'tree'=>scan_dir_recursive($base)];
}

function scan_dir_recursive($dir, $rel=''){
  $items = [];
  foreach(scandir($dir) as $f){
    if($f=='.'||$f=='..') continue;
    $path = $dir . DIRECTORY_SEPARATOR . $f;
    $r = ltrim($rel.'/'.$f,'/');
    if(is_dir($path)){
      $items[] = [
        'name'=>$f,
        'rel'=>$r,
        'children'=>scan_dir_recursive($path, $r)
      ];
    }
  }
  return $items;
}

function move_file(){
  $src  = $_POST['src'] ?? '';
  $dest = $_POST['dest'] ?? '';
  if(!$src || !$dest) throw new Exception('参数错误');

  $srcPath  = abs_path_for_existing($src);  // 校验源路径
  $destPath = abs_path_for_target($dest);   // 目标路径

  if(!is_dir($destPath)) throw new Exception('目标不是文件夹');

  // 目标路径 + 原始名称
  $target = $destPath . DIRECTORY_SEPARATOR . basename($srcPath);

  // 如果已存在同名，自动重命名
  $target = auto_rename_if_exists($target);

  if(@rename($srcPath, $target)){
    return ['ok'=>true];
  } else {
    throw new Exception('移动失败');
  }
}


function zip_file(){
  $paths = json_decode($_POST['paths'] ?? '[]', true);
  if(!$paths) throw new Exception('没有文件');

  $zipFile = TMP_DIR . '/' . uniqid() . '.zip';
  $zip = new ZipArchive();
  if($zip->open($zipFile, ZipArchive::CREATE)!==true){
    throw new Exception('无法创建压缩包');
  }

  foreach($paths as $p){
    $real = abs_path_for_existing($p); // 校验合法路径

    // 确保只打包 BASE_DIR 范围内的文件
    if(strpos($real, realpath(BASE_DIR))!==0) continue;

    if(is_dir($real)){
      $it = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($real, FilesystemIterator::SKIP_DOTS),
        RecursiveIteratorIterator::LEAVES_ONLY
      );
      foreach($it as $file){
        if($file->isFile()){
          $relPath = $p . '/' . substr($file->getPathname(), strlen($real)+1);
          $zip->addFile($file->getRealPath(), $relPath);
        }
      }
    } else {
      $zip->addFile($real, basename($real));
    }
  }
  $zip->close();

  return ['ok'=>true, 'url'=>'/tmp/'.basename($zipFile)];
}

function save_file(){
  $rel = $_POST['path'] ?? '';
  $content = $_POST['content'] ?? '';
  $abs = abs_path_for_existing($rel);
  if(!is_file($abs)) throw new Exception('文件不存在');
  file_put_contents($abs, $content);
  return ['ok'=>true];
}
function new_file(){
  $path = norm_rel($_POST['path'] ?? '');
  $name = trim($_POST['name'] ?? '');
  if($name==='') throw new Exception('文件名为空');
  $target = abs_path_for_target(($path ? $path.'/' : '').$name);
  if(file_exists($target)) throw new Exception('文件已存在');
  file_put_contents($target, ''); // 创建空文件
  return ['ok'=>true];
}
function autoInit(){
  foreach ([BASE_DIR, TMP_DIR, BASE_DIR.'/.thumbs'] as $d) if(!is_dir($d)) @mkdir($d, 0775, true);
}

function json($arr){ echo json_encode($arr, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); }

function norm_rel($rel){ $rel = trim(str_replace(['\\','..'], ['/',''], $rel), '/'); return $rel; }
function abs_path_for_existing($rel){ $rel = norm_rel($rel); $abs = realpath(BASE_DIR . DIRECTORY_SEPARATOR . $rel); if($abs===false || strpos($abs, realpath(BASE_DIR))!==0) throw new Exception('非法路径'); return $abs; }
function abs_path_for_target($rel){ $rel = norm_rel($rel); $abs = BASE_DIR . DIRECTORY_SEPARATOR . $rel; $root = realpath(BASE_DIR); $realParent = realpath(dirname($abs)); if($realParent!==false && strpos($realParent, $root)!==0) throw new Exception('非法路径'); return $abs; }

function listing(){
  $path = norm_rel($_GET['path'] ?? '');
  $abs = BASE_DIR . ($path? DIRECTORY_SEPARATOR.$path : '');
  if(!is_dir($abs)) $abs = BASE_DIR; // fallback
  $items = [];$bytes=0;$total=0;
  $dir = new DirectoryIterator($abs);
  foreach($dir as $f){ if($f->isDot()) continue; $total++;
    $rel = trim(($path? $path.'/' : '').$f->getFilename(), '/');
    if($f->isDir()){
      $items[] = ['name'=>$f->getFilename(),'rel'=>$rel,'type'=>'dir','size'=>null,'mtime'=>date('Y-m-d H:i:s',$f->getMTime()),'isImage'=>false,'isAudio'=>false];
    } else {
        $abs = abs_path_for_existing($rel);
  if(!is_file($abs)) throw new Exception('文件不存在');
  $mime = mime($abs);
         $isAudio = str_starts_with($mime,'audio/') 
           || preg_match('/\.(mp3|wav|ogg|flac|aac|m4a)$/i',$abs);
           
      $size = $f->getSize(); $bytes += $size; $mime = mime($f->getPathname());
      $items[] = [
        'name'=>$f->getFilename(),'rel'=>$rel,'type'=>'file','size'=>$size,'mtime'=>date('Y-m-d H:i:s',$f->getMTime()),
        'isImage'=> str_starts_with($mime,'image/')
        ,'isAudio'=>$isAudio
      ];
    }
  }
  // 目录优先，再按名称
  //usort($items, function($a,$b){ if($a['type']!==$b['type']) return $a['type']==='dir'?-1:1; return strnatcasecmp($a['name'],$b['name']); });
  
  usort($items, function($a,$b){
    // 目录优先
    if($a['type']!==$b['type']) return $a['type']==='dir'?-1:1;
    // 按修改时间降序
    return strtotime($b['mtime']) <=> strtotime($a['mtime']);
});

  
  return ['ok'=>true,'path'=>$path,'items'=>$items,'bytes'=>$bytes,'total'=>$total];
}

function mkdir_p(){
  $path = norm_rel($_POST['path'] ?? '');
  $name = trim($_POST['name'] ?? '');
  if($name==='') throw new Exception('名称为空');
  if(preg_match('/[\\\:*?"<>|]/u',$name)) throw new Exception('名称包含非法字符');
  $abs = abs_path_for_target(($path? $path.'/' : '').$name);
  if(is_dir($abs)) return ['ok'=>true];
  if(!@mkdir($abs, 0775, true)) throw new Exception('创建失败');
  return ['ok'=>true];
}

function rename_item(){
  $rel = $_POST['path'] ?? '';
  $abs = abs_path_for_existing($rel);
  $new = trim($_POST['name'] ?? '');
  if($new==='') throw new Exception('新名称为空');
  if(preg_match('/[\\\:*?"<>|]/u',$new)) throw new Exception('名称包含非法字符');
  $target = dirname($abs) . DIRECTORY_SEPARATOR . $new;
  if(!@rename($abs, $target)) throw new Exception('重命名失败');
  return ['ok'=>true];
}

function delete_items(){
  $paths = json_decode($_POST['paths'] ?? '[]', true) ?: [];
  foreach($paths as $rel){ $abs = abs_path_for_existing($rel); if(is_file($abs)) @unlink($abs); }
  return ['ok'=>true];
}

function rrmdir($dir){ if(!is_dir($dir)) return; $it=new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS); $ri=new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); foreach($ri as $file){ $file->isDir()? @rmdir($file): @unlink($file);} @rmdir($dir); }
function delete_folder(){ $rel = $_POST['path'] ?? ''; $abs = abs_path_for_existing($rel); if(!is_dir($abs)) throw new Exception('不是文件夹'); rrmdir($abs); return ['ok'=>true]; }

function mime($path){ $finfo = finfo_open(FILEINFO_MIME_TYPE); $m = finfo_file($finfo, $path); finfo_close($finfo); return $m ?: 'application/octet-stream'; }

function thumb(){
  // 输出缩略图（缓存至 storage/.thumbs）
  header('Content-Type: image/jpeg');
  $rel = $_GET['path'] ?? ''; $abs = abs_path_for_existing($rel);
  $mime = mime($abs); if(strpos($mime,'image/')!==0){ readfile(__DIR__.'/'); http_response_code(404); exit; }
  $hash = md5($rel.'|'.filemtime($abs)); $cache = BASE_DIR.'/.thumbs/'.$hash.'.jpg';
  if(is_file($cache)){ readfile($cache); exit; }
  [$w,$h] = getimagesize($abs);
  $max=96; $ratio=min($max/$w, $max/$h, 1); $nw=max(1,(int)($w*$ratio)); $nh=max(1,(int)($h*$ratio));
  $src = imagecreatefromstring(file_get_contents($abs)); $dst = imagecreatetruecolor($nw, $nh);
  imagecopyresampled($dst,$src,0,0,0,0,$nw,$nh,$w,$h); imagejpeg($dst,$cache,82); imagejpeg($dst); imagedestroy($src); imagedestroy($dst); exit;
}

function raw(){
  $rel = $_GET['path'] ?? ''; $abs = abs_path_for_existing($rel);
  $mime = mime($abs); header('Content-Type: '.$mime); readfile($abs); exit;
}

function info(){
  $rel = $_GET['path'] ?? ''; 
  $abs = abs_path_for_existing($rel);
  if(!is_file($abs)) throw new Exception('文件不存在');
  $mime = mime($abs);

  $isImage = str_starts_with($mime,'image/');
  $isText  = preg_match('#^(text/|application/(json|xml|javascript))#',$mime) 
           || preg_match('/\.(md|txt|csv|log|json|xml|js|css|html?)$/i',$abs);

  $isVideo = str_starts_with($mime,'video/') 
           || preg_match('/\.(mp4|webm|ogg|mov|avi|mkv)$/i',$abs);

  $isAudio = str_starts_with($mime,'audio/') 
           || preg_match('/\.(mp3|wav|ogg|flac|aac|m4a)$/i',$abs);

  $text = null; 
  if($isText){ 
    $text = file_get_contents($abs, false, null, 0, 1024*1024); 
  }

  return ['ok'=>true,'data'=>[
    'name'=>basename($abs),
    'size'=>filesize($abs),
    'mtime'=>date('Y-m-d H:i:s', filemtime($abs)),
    'mime'=>$mime,
    'isImage'=>$isImage,
    'isText'=>$isText,
    'isVideo'=>$isVideo,
    'isAudio'=>$isAudio,
    'text'=>$text
  ]];
}


// ========== 分片上传 ==========
function check_chunks(){
  $uid = $_GET['uid'] ?? '';
  $dir = TMP_DIR . DIRECTORY_SEPARATOR . $uid;
  $arr = [];
  if(is_dir($dir)){
    foreach(glob($dir.'/*.part') as $p){ $arr[] = (int)basename($p,'.part'); }
  }
  return ['ok'=>true,'uploaded'=>$arr];
}

function upload_chunk(){
  $uid = $_POST['uid'] ?? '';
  $index = (int)($_POST['index'] ?? 0);
  if(!isset($_FILES['file'])) throw new Exception('no file');
  $dir = TMP_DIR . DIRECTORY_SEPARATOR . $uid; if(!is_dir($dir)) @mkdir($dir, 0775, true);
  $tmp = $dir . DIRECTORY_SEPARATOR . $index . '.part';
  if(!move_uploaded_file($_FILES['file']['tmp_name'], $tmp)) throw new Exception('保存分片失败');
  return ['ok'=>true];
}

function merge_chunks(){
  $uid = $_POST['uid'] ?? '';
  $rel = $_POST['rel'] ?? ($_POST['name'] ?? '');
  $target = abs_path_for_target(norm_rel($_POST['path'] ?? '') . ($rel?'/'.$rel:''));

  // 确保目标目录存在
  @mkdir(dirname($target), 0775, true);

  // 如果文件已存在，生成一个新的文件名 xxx_1.ext
  $target = auto_rename_if_exists($target);

  $dir = TMP_DIR . DIRECTORY_SEPARATOR . $uid; 
  if(!is_dir($dir)) throw new Exception('分片不存在');

  $parts = glob($dir.'/*.part'); 
  sort($parts, SORT_NATURAL);

  $out = fopen($target, 'wb');
  foreach($parts as $p){
    $in = fopen($p,'rb'); 
    stream_copy_to_stream($in,$out); 
    fclose($in);
  }
  fclose($out);

  // 清理临时分片
  foreach($parts as $p) @unlink($p); 
  @rmdir($dir);

  return ['ok'=>true, 'path'=>str_replace(BASE_DIR.'/', '', $target)];
}

/**
 * 如果文件已存在，在文件名后追加 _1、_2 ……
 */
function auto_rename_if_exists($path){
  if(!file_exists($path)) return $path;
  $dir  = dirname($path);
  $ext  = pathinfo($path, PATHINFO_EXTENSION);
  $name = pathinfo($path, PATHINFO_FILENAME);

  $i = 1;
  do {
    $newName = $name . '_' . $i . ($ext ? '.' . $ext : '');
    $newPath = $dir . DIRECTORY_SEPARATOR . $newName;
    $i++;
  } while(file_exists($newPath));
  return $newPath;
}

?>